Podman 相关
Table of Contents
1. 容器 Volume
1.1. SELinux 在 volume 上的坑
不开 SELinux 的情况下,执行 podman run ... -v /local/path:/container/path ...
容器可正常挂载 volume 并使用。
在 SELinux 下,用户创建的文件或文件夹的默认 SELinux Type 为 user_home_t,而 SELinux 里面对于容器的规则会让容器无法读写 SELinux Type 为 user_home_t 的文件。
命令改成 podman run ... -v /local/path:/container/path:Z ...
则系统会将挂载点的 SELinux Type 改为 container_file_t:s0...,比如 container_file_t:s0:c191,c797, 表示这个 volume 只由一个容器独占。
也能将命令改成 podman run ... -v /local/path:/container/path:z ...
系统会将挂载点的 SELinux Type 改为 container_file_t:s0 表示这个 volume 可以给多个容器分享。
注意: -v 参数最后的大写 Z 表示这个卷由一个容器独占,小写 z 表示卷可以在多个容器间共享。
2. 容器网络
2.1. 执行 firewall-cmd --reload 之后容器断网
sudo firewall-cmd --reload
之后防火墙会根据配置文件重新生成流量过滤规则,但是 podman 容器的流量过滤规规则没有被 firewalld 保存在文件中,所以此时容器的规则就全部丢失了。
同时,防火墙 reload 之后,podman 并不会重新为容器添加过滤规则,这就导致了所有依赖 firewalld 的容器都断网了。podman 仅仅只是在容器启动的时候,在 firewall 中为这个容器添加了一次规则,就没有然后了。
bridge 网络模式的容器的流量转发靠的是 firewalld,而 网络模式为 host 的容器的网络流量由于不走 firewalld 转发 ,所以使用 host 网络模式的容器并不受影响。
这个问题 2020 年就出现了,这里 https://github.com/containers/podman/issues/5431 也讨论了这个问题。结论是目前无解。
在为 pod container 开放端口时,不能用:
sudo firewall-cmd --zone=public --add-service=http --permanent sudo firewall-cmd --zone=public --add-service=dns --permanent sudo firewall-cmd --reload
应该用:
sudo firewall-cmd --zone=public --add-service=http --permanent sudo firewall-cmd --zone=public --add-service=http sudo firewall-cmd --zone=public --add-service=dns --permanent sudo firewall-cmd --zone=public --add-service=dns
这是 bug 的表现:
[root@rhel8a zones]# sudo firewall-cmd --zone=trusted --list-all trusted (active) target: ACCEPT icmp-block-inversion: no interfaces: sources: 10.88.0.5/32 10.88.0.6/32 services: ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: [root@rhel8a zones]# podman inspect vlmcsd | grep IPA "IPAddress": "10.88.0.6", "IPAddress": "10.88.0.6", "IPAMConfig": null, [root@rhel8a zones]# sudo firewall-cmd --reload success [root@rhel8a zones]# sudo firewall-cmd --zone=trusted --list-all trusted target: ACCEPT icmp-block-inversion: no interfaces: sources: services: ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:
2.2. root 容器使用 bridge 网络模式打开的端口会绕开 firewalld
root 用户的,网络模式为 bridge 的容器启动之后,相应端口直接就是打开的,不需要操作 firewalld。
root 用户的,网络模式为 host 的容器启动之后,需要正常打开端口。
而普通用户的容器的网络模式不论是 bridge 还是 host,容器启动之后,都需要在 firewalld 中打开对应端口。
2.3. rootless 容器不能使用低于 1024 的端口号
在 Linux 上,非特权用户无法打开低于端口号 1024 的端口,这个限制同样适用于 Podman。
因此,默认情况下,rootless 容器无法暴露低于端口号 1024 的端口。
要允许所有非特权应用程序绑定到低于1024的端口,可以使用以下命令解除此限制: sysctl net.ipv4.ip_unprivileged_port_start=0
要永久解除此限制,运行: sysctl -w net.ipv4.ip_unprivileged_port_start=0
从 net.ipv4.ip_unprivileged_port_start 指定端口开始的端口都是 unprivileged port,例子里表示从端口号 0 及 0 之后的端口都是 unprivileged port,即,没有特权端口。
或者,可以用防火墙的端口转发功能,将流向特权端口的流量转发到非特权端口上。
3. ID 映射 (userns)
在 rootless 容器中,podman 自动管理 ID 映射的选项是 --userns
它有如下可选值:
""
默认空值,等于--userns=host.
。将当前用户的 UID 映射为容器内 root 用户的 UIDkeep-id
将当前用户映射到容器内。即,在容器内创建与容器外用户具有相同 UID 的用户keep-id:uid=<uid>,gid=<gid>
将当前用户映射到容器内的 <uid>:<gid> 账户auto
当前用户的 UID 不被映射到容器内,容器内 root 用户的 UID 会被赋一个没有被占用的值。从 /etc/{subuid,subgid} 选 ID 时从没有被占用的部分选 1024 个或更多的 ID 给容器使用 (其他选项默认使用 /etc/{subid,subgid} 指定的所有范围,导致不同容器使用的 ID 范围可能重合)nomap
当前用户的 UID 不被映射到容器内,将 /etc/subuid 内写明可用的,第一个 UID 作为容器内 root 用户的 UID
用户也可以用 --uidmap <start-number-in-container>:<start-number-in-host>:<range>
自己管理到容器的 ID 映射,用法 --uidmap 0:100000:5000
表示,将 host 上 100000 开始的 5000 个 ID 映射到容器中,并且这些 ID 在容器内从 0 开始 (100000 对应容器内 0, 100001 对应 1 ... 104999 对应 5000)。选项 --gidmap
的功能与 --uidmap
类似。它们与 --userns
冲突。
在容器内执行 cat /proc/self/{uid_map,gid_map}
可以查看容器中的 ID 映射规则,输出格式也为 <start-number-in-container>:<start-number-in-host>:<range>。
4. systemd 管理容器
和 docker 不同, podman run --restart ( unless-stopped | always ) ...
之后,如果机器重启,容器会被系统停止,并且容器不随机器一同重启。
如果使用 systemd 来管理容器启停,那么 podman run ... --restart ...
参数绝对不能用。
4.1. 系统级别的容器
若要让容器随机器一同重启,需要自已写一个 .service 文件放在 /etc/systemd/system 下。
.service 文件的内容可以用 podman generate systemd --name <container_name>
生成。
下面的命令可以将输出重定向到 /etc/systemd/system 下,一步到位:
podman generate systemd \ --name <container_name> \ > /etc/systemd/system/container-<container_name>.service
在命令中给定 --new
参数表示每次重启时从镜象生成新的容器,比如:
podman generate systemd --new \ --name <container_name> \ > /etc/systemd/system/container-<container_name>.service
也可以在 podman generate systemd --files --name <container_name>
生成 .service 文件后,执行 mv container-<container_name>.service /etc/systemd/system/container-<container_name>.service
把文件放在 /etc/systemd/system 下。
注意: 如果开了 SELinux,在移动 .service 文件后还要用 restorecon -v /etc/systemd/system/container-<container_name>.service
重新给文件正确的权限。
然后执行 systemctl enable --now <container_name>.service
即可让指定的容器开机启动。
4.2. 用户级别的容器
4.2.1. 分配用户管理器
只需要执行 loginctl enable-linger [USER…]
启用 / 禁止用户逗留 ( 相当于保持登录状态 )。
如果指定了用户名或 UID,那么系统将会在启动时自动为这些用户派生出用户管理器,并且在用户登出后继续保持运行。这样就可以允许未登录的用户在后台运行持续时间很长的服务。
注意: root 用户也不例外。不配置 enable-linger 的情况下,不登陆是不会被分配用户管理器的。
如果没有指定任何参数,那么将作用于当前调用者的用户。
执行 loginctl disable-linger [USER…]
取消操作。
4.2.2. 用户级的 .service 文件
制作 .service 文件的方法参考系统级容器 .service 文件的生成方法。
然后放一个归属普通用户的 vlmcsd 容器的例子:
#!/bin/bash
podman run \
-d \
--name vlmcsd \
--network bridge \
-p 1688:1688 \
docker.io/mikolatero/vlmcsd:latest
# --restart unless-stopped \
############
#
# podman generate systemd --new \
# --name vlmcsd \
# > $HOME/.config/systemd/user/container-vlmcsd.service
#
############
mkdir -p $HOME/.config/systemd/user
podman generate systemd --new --files --name vlmcsd
mv container-vlmcsd.service $HOME/.config/systemd/user/container-vlmcsd.service
restorecon -v $HOME/.config/systemd/user/container-vlmcsd.service
systemctl --user enable container-vlmcsd
sudo firewall-cmd --add-port=1688/tcp --permanent
sudo firewall-cmd --add-port=1688/tcp
4.3. podman 生成 systemd 文件中的坑
如果用 systemd 管理容器启停,那么 podman run ... --restart ...
这个参数绝对不能用。如果没有加这个参数, podman generate systemd
生成的 .service 文件中,服务启动时执行的命令如下:
... ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --sdnotify=conmon --cgroups=no-conmon --rm --replace -d --name adguardhome ...... ...
注意里面有一个 podman run ... --rm ...
参数,参数 --rm
和 --restart
冲突。如果在首次生成容器时的命令用 podman run ... --restart ...
,那么,由 podman generate systemd
得到的 .service 文件中的启动命令会变成 podman run ... --rm ... --restart ...
。这个命令是无法启动容器的。
sudo journalctl -n 100
可以看到日志中有类似这样的报错:
... Error: the --rm option conflicts with --restart, when the restartPolicy is not "" and "no" ...